# frozen_string_literal: true

module MergeRequests
  class CreateFromVulnerabilityDataService < ::BaseService
    def execute
      begin
        vulnerability = Gitlab::Vulnerabilities::Parser.fabricate(@params)
      rescue Gitlab::Vulnerabilities::InvalidCategoryError
        return error('Invalid vulnerability category')
      end

      title_slug = Gitlab::Utils.slugify(vulnerability.title || "")
      source_branch = "remediate/%s-%s" % [
        title_slug[0..74],
        Time.current.strftime("D%Y%m%dT%H%M%S")
      ]
      target_branch = vulnerability.target_branch || @project.default_branch

      return error("User is not permitted to create merge request") unless can_create_merge_request?(source_branch)
      return error("No remediations available for merge request") if vulnerability.remediations.blank?

      patch_result = create_patch(vulnerability, source_branch, target_branch)

      return error('Unable to apply patch') unless patch_result[:status] == :success

      merge_request_params = {
        title: "Resolve vulnerability: #{vulnerability.title}",
        description: render_description(vulnerability),
        source_branch: source_branch,
        target_branch: target_branch,
        force_remove_source_branch: "1"
      }

      merge_request = MergeRequests::CreateService.new(project: @project, current_user: @current_user, params: merge_request_params).execute

      if merge_request.valid?
        success(merge_request)
      else
        error(merge_request.errors)
      end
    end

    private

    def create_patch(vulnerability, source_branch, target_branch)
      diffs = vulnerability.data[:remediations].map { |remediation| Base64.decode64(remediation[:diff]) }
      head_commit = project.repository.find_branch(target_branch).dereferenced_target
      new_commit = render_commit(diffs, head_commit, vulnerability)

      commit_patch_params = {
        branch_name: source_branch,
        start_branch: target_branch,
        patches: [
          new_commit
        ]
      }
      Commits::CommitPatchService.new(@project, @current_user, commit_patch_params).execute
    end

    def success(merge_request)
      super(merge_request: merge_request)
    end

    def render_commit(diffs, head_commit, vulnerability)
      git_user = Gitlab::Git::User.from_gitlab(@current_user)

      # This currently applies only the first diff
      render_template(
        file: 'vulnerabilities/remediation',
        formats: :patch,
        locals: {
          diff: diffs.first,
          head_commit: head_commit,
          user: git_user,
          vulnerability: vulnerability
        }
      )
    end

    def render_description(vulnerability)
      render_template(
        file: 'vulnerabilities/merge_request_description',
        formats: :md,
        locals: { vulnerability: vulnerability }
      )
    end

    def render_template(file:, locals:, formats:)
      ApplicationController.render(template: file, locals: locals, formats: formats)
    end

    def can_create_merge_request?(source_branch)
      can?(@current_user, :create_merge_request_in, @project) &&
        can?(@current_user, :create_merge_request_from, @project) &&
        ::Gitlab::UserAccess.new(@current_user, container: @project).can_push_to_branch?(source_branch)
    end
  end
end
